/**
 * Copyright (c) 2019 Coder League
 * All rights reserved.
 *
 * File：UserService.java
 * History:
 *         2019年5月11日: Initially created, Chrise.
 */
package club.coderleague.ilsp.service.users;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.servlet.http.HttpSession;
import javax.validation.ValidationException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import club.coderleague.data.jpa.domain.Page;
import club.coderleague.ilsp.common.domain.beans.PagingParameters;
import club.coderleague.ilsp.common.domain.beans.UserSession;
import club.coderleague.ilsp.common.domain.beans.UsersExtension;
import club.coderleague.ilsp.common.domain.enums.EntityState;
import club.coderleague.ilsp.common.domain.enums.UserType;
import club.coderleague.ilsp.common.exception.MessageInfoException;
import club.coderleague.ilsp.common.exception.MessageWarnException;
import club.coderleague.ilsp.dao.OrganizationsMgrDao;
import club.coderleague.ilsp.dao.RolesDao;
import club.coderleague.ilsp.dao.UserAuthsDao;
import club.coderleague.ilsp.dao.UserDao;
import club.coderleague.ilsp.entities.Organizations;
import club.coderleague.ilsp.entities.Roles;
import club.coderleague.ilsp.entities.Userauths;
import club.coderleague.ilsp.entities.Users;
import club.coderleague.ilsp.service.AuthenticationService;
import club.coderleague.ilsp.util.CommonUtil;
import club.coderleague.security.AlgorithmBeanFactory;
import club.coderleague.security.algorithm.AESEncryptor;
import club.coderleague.security.algorithm.MD5Signer;
import club.coderleague.security.support.CiphertextMode;

/**
 * 用户服务。
 * @author Chrise
 */
@Service
public class UserService implements AuthenticationService {
	private static final Pattern PHONE_REG = Pattern.compile("^1\\d{10}$");
	
	@Value("${custom.dev-mode:false}")
	private boolean devMode;
	
	@Autowired
	private UserDao userDao;
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	/**
	 * 用户授权Dao
	 */
	private @Autowired UserAuthsDao userAuthsDao;
	
	/**
	 * 机构Dao
	 */
	private @Autowired OrganizationsMgrDao organizationsMgrDao;
	
	/**
	 * 角色Dao
	 */
	private @Autowired RolesDao rolesDao;
	
	/**
	 * @see club.coderleague.ilsp.service.AuthenticationService#authenticate(java.lang.String, java.lang.String)
	 */
	@Override
	public boolean authenticate(String username, String password) {
		Users admin = this.userDao.queryAdministrator();
		if (admin != null && admin.getLoginname().equals(username) && this.passwordEncoder.matches(password, admin.getLoginpassword())) return true;
		return false;
	}
	
	/**
	 * 注册管理员。
	 * @author Chrise 2019年5月11日
	 */
	public void execRegisterAdminstrator() {
		if (this.userDao.isAdministratorExists()) return;
		
		Users user = new Users(EntityState.VALID.getValue(), null, "Adminstrator", "", "", "admin", 
			this.passwordEncoder.encode("admin"), UserType.ADMIN.getValue());
		this.userDao.save(user);
	}
	
	/**
	 * 用户登录。
	 * @author Chrise 2019年5月12日
	 * @param username 用户名。
	 * @param password 密码。
	 * @param vericode 验证码。
	 * @param cachecode 缓存验证码。
	 * @param session 会话对象。
	 * @return 登录结果消息。
	 * @throws Exception 业务处理异常。
	 */
	public String userLogin(String username, String password, String vericode, String cachecode, HttpSession session) 
		throws Exception {
		if (!this.devMode && (CommonUtil.isEmpty(vericode) || !vericode.toUpperCase().equals(cachecode))) 
			return "验证码错误！";
		
		if (CommonUtil.isEmpty(username)) return "用户不存在！";
		if (CommonUtil.isEmpty(password)) return "密码错误！";
		
		if (PHONE_REG.matcher(username).matches()) {
			String sign = AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class).sign(username);
			UserSession us = this.userDao.queryUserSession(sign, true);
			if (us != null) {
				if (this.passwordEncoder.matches(password, us.getPassword())) {
					this.queryAuthorities(us);
					session.setAttribute(UserSession.SESSION_KEY, us);
					return null;
				}
				return "密码错误！";
			}
		}
		
		UserSession us = this.userDao.queryUserSession(username, false);
		if (us == null) return "用户不存在！";
		if (this.passwordEncoder.matches(password, us.getPassword())) {
			this.queryAuthorities(us);
			session.setAttribute(UserSession.SESSION_KEY, us);
			return null;
		}
		
		return "密码错误！";
	}
	
	/**
	 * 查询授权标识。
	 * @author Chrise 2019年5月21日
	 * @param userid 用户标识。
	 * @return 授权标识集合。
	 */
	public List<String> queryAuthorities(Long userid) {
		return this.userDao.queryAuthorities(userid);
	}
	
	/**
	 * 查询授权。
	 * @author Chrise 2019年5月16日
	 * @param us 用户会话对象。
	 */
	private void queryAuthorities(UserSession us) {
		if (UserType.ADMIN.equalsValue(us.getUsertype())) us.setAuthorities(this.userDao.queryAuthorities(null));
		else us.setAuthorities(this.userDao.queryAuthorities(us.getUserid()));
	}

	/**
	 * 分页查询用户
	 * 
	 * @author CJH 2019年7月2日
	 * @param pagingparameters 分页参数
	 * @param isrecycle 是否回收站
	 * @param keyword 关键字
	 * @param usernametext 用户名
	 * @param username 用户名对应用户主键
	 * @param userphonetext 用户电话
	 * @param userphone 用户电话对应用户主键
	 * @return 用户
	 */
	public Page<UsersExtension> findPageByParamsMap(PagingParameters pagingparameters, boolean isrecycle, String keyword, String usernametext, String username, String userphonetext, String userphone) {
		// 加密用户名
		String usernameaes = null;
		// 加密用户电话
		String userphoneaes = null;
		try {
			if (StringUtils.isNoneBlank(usernametext, username)) {
				String usernamemd5 = AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class).sign(username);
				usernameaes = AlgorithmBeanFactory.getAlgorithmBean(AESEncryptor.class).encrypt(usernametext, usernamemd5, CiphertextMode.BASE64);
			}
			if (StringUtils.isNoneBlank(userphonetext, userphone)) {
				String userphonemd5 = AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class).sign(userphone);
				userphoneaes = AlgorithmBeanFactory.getAlgorithmBean(AESEncryptor.class).encrypt(userphonetext, userphonemd5, CiphertextMode.BASE64);
			}
		} catch (InvalidKeyException | UnsupportedEncodingException | NoSuchAlgorithmException
				| NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
			e.printStackTrace();
			throw new MessageWarnException("用户名或用户电话无法加密");
		}
		return userDao.findPageByParamsMap(pagingparameters, isrecycle, keyword, usernameaes, userphoneaes);
	}

	/**
	 * 新增用户
	 * 
	 * @author CJH 2019年5月17日
	 * @param users 用户
	 * @param roleids 角色主键
	 */
	public void insert(Users users, String roleids) {
		try {
			users.setPhonehash(AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class).sign(users.getUserphone()));
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new MessageWarnException("用户电话无法加密");
		}
		if (userDao.existsByPhonehashOrLoginnameNotEntityid(users.getPhonehash(), users.getLoginname(), null)) {
			throw new MessageInfoException("登录名或用户电话已存在");
		}
		users.setEntitystate(EntityState.VALID.getValue());
		users.setLoginpassword(passwordEncoder.encode(users.getLoginpassword()));
		users.setUsertype(UserType.NORMAL.getValue());
		userDao.save(users);
		
		if (StringUtils.isNotBlank(roleids)) {
			for (String roleid : roleids.split(",")) {
				userAuthsDao.save(new Userauths(EntityState.VALID.getValue(), users.getEntityid(), Long.parseLong(roleid)));
			}
		}
	}

	/**
	 * 更新用户
	 * 
	 * @author CJH 2019年5月17日
	 * @param users 用户
	 * @param roleids 角色主键
	 */
	public void update(Users users, String roleids) {
		if (users.getEntityid() == null) {
			throw new MessageWarnException("用户主键不能为空");
		}
		try {
			users.setPhonehash(AlgorithmBeanFactory.getAlgorithmBean(MD5Signer.class).sign(users.getUserphone()));
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new MessageWarnException("用户电话无法加密");
		}
		if (userDao.existsByPhonehashOrLoginnameNotEntityid(users.getPhonehash(), users.getLoginname(), users.getEntityid())) {
			throw new MessageInfoException("登录名或用户电话已存在");
		}
		Users usersSource = userDao.getOne(users.getEntityid());
		usersSource.setOrgid(users.getOrgid());
		usersSource.setUsername(users.getUsername());
		usersSource.setUserphone(users.getUserphone());
		usersSource.setPhonehash(users.getPhonehash());
		if (StringUtils.isNotBlank(users.getLoginpassword())) {
			usersSource.setLoginpassword(passwordEncoder.encode(users.getLoginpassword()));
		}
		
		List<Long> validentityids = new ArrayList<>();
		if (StringUtils.isNotBlank(roleids)) {
			for (String roleidstr : roleids.split(",")) {
				Long roleid = Long.parseLong(roleidstr);
				Userauths userauths = userAuthsDao.findOneByUseridAndRoleid(users.getEntityid(), roleid);
				if (userauths != null) {
					if (EntityState.INVALID.equalsValue(userauths.getEntitystate())) {
						userauths.setEntitystate(EntityState.VALID.getValue());
					}
					validentityids.add(userauths.getEntityid());
				} else {
					userauths = new Userauths(EntityState.VALID.getValue(), users.getEntityid(), roleid);
					userAuthsDao.save(userauths);
					validentityids.add(userauths.getEntityid());
				}
			}
		}
		userAuthsDao.updateEntitystateByUseridNotInEntityids(EntityState.INVALID.getValue(), users.getEntityid(), validentityids);
	}

	/**
	 * 查询所有机构
	 * 
	 * @author CJH 2019年5月20日
	 * @return 机构
	 */
	public List<Organizations> findAllOrganizations() {
		return organizationsMgrDao.findAllByEntitystate(EntityState.VALID.getValue());
	}

	/**
	 * 查询授权角色
	 * 
	 * @author CJH 2019年5月20日
	 * @return 角色
	 */
	public List<Roles> findAuthRoles() {
		return rolesDao.findAuth();
	}

	/**
	 * 根据用户主键查询用户
	 * 
	 * @author CJH 2019年5月20日
	 * @param entityid 用户主键
	 * @return 用户
	 */
	public UsersExtension findExtensionByEntityid(Long entityid) {
		if (entityid == null) {
			throw new ValidationException("用户主键不能为空");
		}
		return userDao.findExtensionByEntityid(entityid);
	}
	
	/**
	 * 更新用户状态
	 * 
	 * @author CJH 2019年5月20日
	 * @param entityids 用户主键
	 * @param entitystate 状态
	 * @param isdistinct 是否去重
	 */
	public void updateEntitystate(String entityids, Integer entitystate, boolean isdistinct) {
		if (StringUtils.isBlank(entityids)) {
			throw new ValidationException("用户主键不能为空");
		}
		for (String entityid : entityids.split(",")) {
			Users users = userDao.getOne(Long.parseLong(entityid));
			if (users == null) {
				continue;
			}
			if (isdistinct && userDao.existsByPhonehashOrLoginnameNotEntityid(users.getPhonehash(), users.getLoginname(), users.getEntityid())) {
				throw new MessageInfoException("登录名或用户电话已存在");
			}
			users.setEntitystate(entitystate);
		}
	}

	/**
	 * 查询所有用户名和用户电话
	 * 
	 * @author CJH 2019年5月22日
	 * @param isrecycle 是否回收站
	 * @return 用户
	 */
	public List<UsersExtension> findAllUsernameAndUserphone(boolean isrecycle) {
		return userDao.findAllUsernameAndUserphone(isrecycle);
	}
}
